﻿using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace KunURP
{
    public class DualKawaseBlur : ScriptableRendererFeature
    {
        [System.Serializable]
        public class Setting
        {
            public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingTransparents;//默认插到透明完成后
            public Material material;
            [Range(0.0f, 5.0f)]
            public float blurRadius = 0.5f;
            [Range(1, 10)]
            public int iterationNum = 6;
            [Range(1, 8)]
            public float rtDownScaling = 2;
        }

        public Setting m_Setting = new Setting();

        class CustomRenderPass : ScriptableRenderPass
        {
            public const int MaxLevel = 16;

            public Material material;
            public float blurRadius;
            public int iterationNum;
            public float rtDownScaling;

            FilterMode passFilterMode{get;set;}//图像的模式
            RenderTargetIdentifier passSource{get;set;}//源图像,目标图像
            RenderTargetIdentifier buffer1;//临时计算图像
            RenderTargetIdentifier buffer2;
            string passTag;

            private Level[] m_Pyramid;

            struct Level
            {
                internal int down;
                internal int up;
            }

            static class ShaderIDs
            {
                internal static readonly int BlurRadius = Shader.PropertyToID("_BlurRadius");
                internal static readonly int BufferRT1 = Shader.PropertyToID("_BufferRT1");
                internal static readonly int BufferRT2 = Shader.PropertyToID("_BufferRT2");
            }

            public CustomRenderPass(string tag)
            {
                this.passTag = tag;
                this.m_Pyramid = new Level[MaxLevel];
                for (int i = 0; i < MaxLevel; i++)
                {
                    this.m_Pyramid[i] = new Level
                    {
                        down = Shader.PropertyToID("_BlurMipDown" + i),
                        up = Shader.PropertyToID("_BlurMipUp" + i),
                    };
                }
            }

            public void Setup(RenderTargetIdentifier passSource)
            {
                this.passSource = passSource;
            }
            
            public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
            {
                CommandBuffer cmd = CommandBufferPool.Get(this.passTag);
                RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
                int width = (int)(opaqueDesc.width / this.rtDownScaling);
                int height = (int)(opaqueDesc.height / this.rtDownScaling);
                this.material.SetFloat(ShaderIDs.BlurRadius, this.blurRadius);
                // Down Sample
                RenderTargetIdentifier lastDown = this.passSource;
                for (int i = 0; i < this.iterationNum; i++)
                {
                    int mipDown = this.m_Pyramid[i].down;
                    int mipUp = this.m_Pyramid[i].up;
                    cmd.GetTemporaryRT(mipDown, width, height, 0, this.passFilterMode);
                    cmd.GetTemporaryRT(mipUp, width, height, 0, this.passFilterMode);
                    cmd.Blit(lastDown, mipDown, this.material, 0);
                    lastDown = mipDown;
                    width = Mathf.Max(width / 2, 1);
                    height = Mathf.Max(height / 2, 1);
                }
                // Up Sample
                int lastUp = this.m_Pyramid[this.iterationNum - 1].down;
                for (int i = this.iterationNum - 2; i >= 0; i--)
                {
                    int mipUp = this.m_Pyramid[i].up;
                    cmd.Blit(lastUp, mipUp, this.material, 1);
                    lastUp = mipUp;
                }

                cmd.Blit(lastUp, this.passSource, this.material, 1);

                context.ExecuteCommandBuffer(cmd);
                CommandBufferPool.Release(cmd);
            }

        }

        CustomRenderPass m_ScriptablePass;

        public override void Create()
        {
            this.m_ScriptablePass = new CustomRenderPass(name);
            this.m_ScriptablePass.renderPassEvent = this.m_Setting.passEvent;
            this.m_ScriptablePass.material = this.m_Setting.material;
            this.m_ScriptablePass.blurRadius = this.m_Setting.blurRadius;
            this.m_ScriptablePass.iterationNum = this.m_Setting.iterationNum;
            this.m_ScriptablePass.rtDownScaling = this.m_Setting.rtDownScaling;

        }

        // Here you can inject one or multiple render passes in the renderer.
        // This method is called when setting up the renderer once per-camera.
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            if (this.m_Setting.material != null) {
                this.m_ScriptablePass.Setup(renderer.cameraColorTarget);
                renderer.EnqueuePass(this.m_ScriptablePass);
            }
        }
    }

}


